#include "tiffwrite.hpp"

/**********************************************************************
 * TIFFWRITE.C
 * Julian Fong
 * August 1995
 * Found at: http://levork.org/tiffwrite.c
 *
 * ***WARNING*** this code is a hack! I'm not responsible for nausea,
 * dizziness, vomiting or chest pains caused by perusing this code.
 *
 * That being said, this is simply code to write a 24 bit RGB TIFF file,
 * based on the Aldus/Microsoft Tech Memorandum/TIFF Revision 5.0/Aug 1988,
 * available from 
 * gopher://palimpsest.stanford.edu:70/00/ByTopic/standards/tiff.5.0.txt
 * (If Adobe had been considerate enough to provide Acrobat for SGI I would
 * have read the Rev 6.0 document instead)
 *
 * The RGB file produced is 24 bit, uncompressed, and has the minimum 14 
 * tags necessary for TIFF RGB images according to Rev 6.0 (at least,
 * as far as I could tell from the Image Format Encyclopedia).
 *
 * The code is very hackish since I'm using precomputed file offsets as 
 * follows:
 *  0: 8 bit header
 *  8: Start of IFD: 2 bytes count (14)
 * 10: NewSubFileType tag
 * 22: ImageWidth tag
 * 34: ImageLength tag
 * 46: BitsPerSamples (points to 182)
 * 58: Compression
 * 70: PhotometricInterpretation
 * 82: StripOffsets (points to 204)
 * 94: SamplesPerPixel
 *106: RowsPerStrip
 *118: StripByteCounts (points to 204 + 4 * height)
 *130: XResolution (points to 188)
 *142: YResolution (points to 196)
 *154: PlanarConfiguration
 *166: ResolutionUnit
 *178: 4 Null bytes (End of IFD)
 *182: 6 bytes of BitsPerSample info
 *188: X Resolution Information (RATIONAL, so two longs = 8 bytes)
 *196: Y Resolution Information (RATIONAL, so two longs = 8 bytes)
 *204: The StripOffsets information.
 *204 + 4 * height: The StripByteCounts information.
 *204 + 8 * height: The Image.
 *
 **********************************************************************/

#include <stdlib.h>
#include <stdio.h>

/* comment this out if you're not using OpenGL.
   It only affects the image being upside down */
//#ifndef OPENGL
//#define OPENGL
//#endif

/* bit of a hack which gives me "I" or "M" depending on byte order.
   This used to be a function, hence the parentheses */
#define end_type() ((*(short *) "IM") & 255)

/* weird TIFF defines */
#define TIFF_BYTE 1
#define TIFF_ASCII 2
#define TIFF_SHORT 3
#define TIFF_LONG 4
#define TIFF_RATIONAL 5
#define NewSubfileType 254
#define ImageWidth 256
#define ImageLength 257
#define BitsPerSample 258
#define Compression 259
#define PhotometricInterpretation 262
#define StripOffsets 273
#define SamplesPerPixel 277
#define RowsPerStrip 278
#define StripByteCounts 279
#define XResolution 282
#define YResolution 283
#define PlanarConfiguration 284
#define ResolutionUnit 296

/* function for writing a tag which has its value pointing elsewhere in the
   file */
static void write_pointer_tag(FILE *fp, short tagnum, short size,
			      long elements, long pointer)
{
  fwrite(&tagnum, sizeof(short), 1, fp);
  fwrite(&size, sizeof(short), 1, fp);
  fwrite(&elements, sizeof(long), 1, fp);
  fwrite(&pointer, sizeof(long), 1, fp);
}

/* function which writes a tag with a short value */
static void write_short_tag(FILE *fp, short tagnum, long elements, 
			    short value)
{
  long templong;
  short size = TIFF_SHORT; /* short */
	
  /* The spec doc says "values which fit in less than 4 bytes are left 
     justified". Thus, due to byte order I need this brain-damaged code.
     On Intel systems, left justified is the default */
  if (end_type() == 'I')
    templong = (long) value;
  /* on Motorola systems, left justified for shorts is shifted left 16 bits */
  else
    templong = (long)value << 16;
  fwrite(&tagnum, sizeof(short), 1, fp);
  fwrite(&size, sizeof(short), 1, fp);
  fwrite(&elements, sizeof(long), 1, fp);
  fwrite(&templong, sizeof(long), 1, fp);
}

/* function which writes a tag with a long value */
static void write_long_tag(FILE *fp, short tagnum, long elements, 
			   long value)
{
  short size = TIFF_LONG; /* long */
  fwrite(&tagnum, sizeof(short), 1, fp);
  fwrite(&size, sizeof(short), 1, fp);
  fwrite(&elements, sizeof(long), 1, fp);
  fwrite(&value, sizeof(long), 1, fp);
}

/* main function */
void tiffwrite(const char *filename, ComposantCouleur *rgb,
	       unsigned long int width,
	       unsigned long int height)
{
  FILE *fp;
  char tempchar;
  short int tempshort;
  long int templong;
  unsigned long int counter;
  long num_bytes_per_row;
  long int byte_counter = 0;
	
  if ((fp = fopen(filename, "w+")) == NULL) // au cas ou on ne peut faire un tiff
    {
      fprintf(stderr, "Error in opening file %s!\n", filename);
      return;
    }
  /* write two byte order chars */
  tempchar = end_type();
  fwrite(&tempchar,1,1,fp);
  fwrite(&tempchar,1,1,fp);
  /* write the 42, the answer to life, the universe, and everything */  
  tempshort = 42;
  fwrite(&tempshort,sizeof(short),1,fp);
  /* IFD address is at 8 */
  templong = 8;
  fwrite(&templong,sizeof(long),1,fp);
	
  /****************************************************/
  /********************* BEGIN IFD ********************/
  /****************************************************/
	
  /* RGB TIFF has minimum 14 tags*/
  tempshort = 14;
  fwrite(&tempshort,sizeof(short), 1, fp);
  /* NewSubfileType is 0, meaning this image is a simple image */
  write_long_tag(fp, NewSubfileType, 1, 0);
  /* Image width and height */
  write_long_tag(fp, ImageWidth, 1, width);
  write_long_tag(fp, ImageLength, 1, height);
  /* BitsPerSample is 8, 8, 8 for RGB */
  write_pointer_tag(fp, BitsPerSample, TIFF_SHORT, 3, 182);
  /* Compression = 1, means no compression */
  write_short_tag(fp, Compression, 1, 1);
  /* PhotometricInterpretation = 2, means Image is an RGB */
  write_short_tag(fp, PhotometricInterpretation, 1, 2);
  /* StripOffsets begin at 204 */
  write_pointer_tag(fp, StripOffsets, TIFF_LONG, height, 204);
  /* RGB image has 3 samples per pixel */
  write_short_tag(fp, SamplesPerPixel, 1, 3);
  /* Each strip represents one row */
  write_long_tag(fp, RowsPerStrip, 1, 1);
  /* StripByteCounts begin at 204 + 4 * height (StripOffsets were longs) */
  write_pointer_tag(fp, StripByteCounts, TIFF_LONG, height, 204 + 4 * height);
  /* pointer to two rational tags */
  write_pointer_tag(fp, XResolution, TIFF_RATIONAL, 1, 188);
  write_pointer_tag(fp, YResolution, TIFF_RATIONAL, 1, 196);
  /* Image is stored as RGBRGBRGBRGB */
  write_short_tag(fp, PlanarConfiguration, 1, 1);
  /* ResUnit = inch.. again, arbitrary */
  write_short_tag(fp, ResolutionUnit, 1, 2);
  /* write 4 null bytes */
  templong = 0;
  fwrite(&templong, sizeof(long), 1, fp);
  /****************************************************/
  /*********************  END IFD  ********************/
  /****************************************************/
  /* write bitspersample info */
  tempshort = 8;
  fwrite(&tempshort, sizeof(short), 1, fp);
  fwrite(&tempshort, sizeof(short), 1, fp);
  fwrite(&tempshort, sizeof(short), 1, fp);
  /* write xres and yres info. Arbitrary chosen to be 72 dpi, since that's
     close to what the monitor is */
  templong = 72;
  fwrite(&templong, sizeof(long), 1, fp);
  templong = 1;
  fwrite(&templong, sizeof(long), 1, fp);
  templong = 72;
  fwrite(&templong, sizeof(long), 1, fp);
  templong = 1;
  fwrite(&templong, sizeof(long), 1, fp);
	
  num_bytes_per_row = width * 3;
  /* strips start at 204 + 8 * height. */
  byte_counter = 204 + 8 * height;
  for (counter = 0; counter < height; counter++)
    {
      fwrite(&byte_counter, sizeof(long), 1, fp);
      byte_counter += num_bytes_per_row;
    }
  /* strip byte counter info */
  for (counter = 0; counter < height; counter++)
    fwrite(&num_bytes_per_row, sizeof(long), 1, fp);
#ifdef OPENGL
  /* write image */
  for (counter = height - 1; counter >= 0; counter--)
    {
      /* the image from OpenGL is upside down.  We have to write
	 rows backwards */
      fwrite(rgb + (counter * num_bytes_per_row), 
	     sizeof(ComposantCouleur), (3 * width), fp);
    }
#else
  /* just dump entire array to disk */
  fwrite(rgb, sizeof(ComposantCouleur), (3 * width * height), fp);
#endif
  /* end */
  fclose(fp);
}
